import sys
import os
from PyQt5.QtWidgets import QAction, QSpinBox, QMessageBox, QApplication, QMainWindow, QGroupBox, QDialogButtonBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QHBoxLayout, QWidget, QMenuBar, QStatusBar, QTextEdit, QAction, QDialog, QLabel, QComboBox, QLineEdit, QPushButton, QFormLayout, QHeaderView
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QIcon 
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtSerialPort import QSerialPort, QSerialPortInfo
import random
from datetime import datetime
import struct
import json

# 串口参数设置显示窗口
class SettingsDialog(QDialog):
    def __init__(self, current_settings, parent=None):
        super().__init__(parent)
        self.setWindowTitle("设备参数设置")
        # 设置窗口大小
        self.resize(500, 300)
        # 设置串口不可调整大小
        self.setFixedSize(self.size())

        main_layout = QVBoxLayout(self)

        # 创建分组：串口设置
        serial_group = QGroupBox("串口设置")
        serial_layout = QFormLayout()
        serial_layout.setSpacing(10)  # 调整控件间距

        self.port_label = QLabel("通信端口：")
        self.port_combo = QComboBox()
        self.scan_ports()
        # 设置当前端口
        if "port" in current_settings and current_settings["port"]:
            index = self.port_combo.findText(current_settings["port"])
            if index != -1:
                self.port_combo.setCurrentIndex(index)
        serial_layout.addRow(self.port_label, self.port_combo)

        self.baudrate_label = QLabel("波特率：")
        self.baudrate_combo = QComboBox()
        self.baudrate_combo.addItems(["9600", "19200", "38400", "57600", "115200"])
        # 设置当前波特率
        if "baudrate" in current_settings and current_settings["baudrate"]:
            index = self.baudrate_combo.findText(str(current_settings["baudrate"]))
            if index != -1:
                self.baudrate_combo.setCurrentIndex(index)
        serial_layout.addRow(self.baudrate_label, self.baudrate_combo)

        serial_group.setLayout(serial_layout)
        main_layout.addWidget(serial_group)

        # 创建分组：采样时间设置
        sampling_group = QGroupBox("采样时间设置")
        sampling_layout = QFormLayout()
        sampling_layout.setSpacing(10)

        self.interval_label = QLabel("设备采样时间间隔：")
        interval_layout = QHBoxLayout()
        self.interval_edit = QLineEdit()
        self.interval_unit_label = QLabel("毫秒")
        interval_layout.addWidget(self.interval_edit)
        interval_layout.addWidget(self.interval_unit_label)
        sampling_layout.addRow(self.interval_label, interval_layout)
        # 设置当前设备采样间隔
        self.interval_edit.setText(str(current_settings.get("device_interval", 5000)))
        # 设置文字居中显示
        self.interval_edit.setAlignment(Qt.AlignCenter)

        self.resistance_label = QLabel("电阻采样时间间隔：")
        resistance_layout = QHBoxLayout()
        self.resistance_edit = QLineEdit()
        self.resistance_unit_label = QLabel("秒")
        resistance_layout.addWidget(self.resistance_edit)
        resistance_layout.addWidget(self.resistance_unit_label)
        sampling_layout.addRow(self.resistance_label, resistance_layout)
        # 设置当前电阻采样间隔 (注意单位转换)
        self.resistance_edit.setText(str(current_settings.get("resistance_interval", 10000) // 1000))
        self.resistance_edit.setAlignment(Qt.AlignCenter)

        sampling_group.setLayout(sampling_layout)
        main_layout.addWidget(sampling_group)

        # 按钮布局
        button_box = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Close)
        button_box.accepted.connect(self.accept)
        button_box.rejected.connect(self.reject)
        main_layout.addWidget(button_box)

        # 添加保存采样时间参数的功能
        self.saved_settings = {}

    def scan_ports(self):
        """扫描可用串口"""
        ports = QSerialPortInfo.availablePorts()
        for port in ports:
            self.port_combo.addItem(port.portName())

    def accept(self):
        """保存设置参数"""
        self.saved_settings = {
            "port": self.port_combo.currentText(),
            "baudrate": self.baudrate_combo.currentText(),
            "device_interval": self.interval_edit.text(),
            "resistance_interval": self.resistance_edit.text()
        }
        super().accept()

    def get_saved_settings(self):
        """获取保存的设置参数"""
        return self.saved_settings

# 告警设置显示窗口
class WarningSettingsDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("告警设置")
        self.setFixedSize(500, 300)

        main_layout = QVBoxLayout(self)

        # 创建分组：OPGW告警设置
        opgw_group = QGroupBox("OPGW告警设置")
        opgw_layout = QFormLayout()
        opgw_layout.setSpacing(10)

        # 在线电阻
        self.resistance_label = QLabel("在线电阻:")
        resistance_layout = QHBoxLayout()
        self.resistance_edit = QLineEdit("15")
        self.resistance_unit_label = QLabel("Ω")  # 单位标签
        resistance_layout.addWidget(self.resistance_edit)
        resistance_layout.addWidget(self.resistance_unit_label)
        opgw_layout.addRow(self.resistance_label, resistance_layout)

        # 感应电流
        self.induction_current_label = QLabel("感应电流:")
        induction_current_layout = QHBoxLayout()
        self.induction_current_edit = QLineEdit("60")
        self.induction_current_unit_label = QLabel("A")  # 单位标签
        induction_current_layout.addWidget(self.induction_current_edit)
        induction_current_layout.addWidget(self.induction_current_unit_label)
        opgw_layout.addRow(self.induction_current_label, induction_current_layout)

        # 系统过压
        self.system_overvoltage_label = QLabel("系统过压:")
        system_overvoltage_layout = QHBoxLayout()
        self.system_overvoltage_edit = QLineEdit("30")
        self.system_overvoltage_unit_label = QLabel("A")  # 单位标签
        system_overvoltage_layout.addWidget(self.system_overvoltage_edit)
        system_overvoltage_layout.addWidget(self.system_overvoltage_unit_label)
        opgw_layout.addRow(self.system_overvoltage_label, system_overvoltage_layout)

        # 雷电过压
        self.lightning_overvoltage_label = QLabel("雷电过压:")
        lightning_overvoltage_layout = QHBoxLayout()
        self.lightning_overvoltage_edit = QLineEdit("50")
        self.lightning_overvoltage_unit_label = QLabel("A")  # 单位标签
        lightning_overvoltage_layout.addWidget(self.lightning_overvoltage_edit)
        lightning_overvoltage_layout.addWidget(self.lightning_overvoltage_unit_label)
        opgw_layout.addRow(self.lightning_overvoltage_label, lightning_overvoltage_layout)

        opgw_group.setLayout(opgw_layout)
        main_layout.addWidget(opgw_group)

        # 按钮布局
        button_box = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Cancel)
        button_box.accepted.connect(self.accept)
        button_box.rejected.connect(self.reject)
        main_layout.addWidget(button_box)

        # 设置默认参数居中显示
        self.resistance_edit.setAlignment(Qt.AlignCenter)
        self.induction_current_edit.setAlignment(Qt.AlignCenter)
        self.system_overvoltage_edit.setAlignment(Qt.AlignCenter)
        self.lightning_overvoltage_edit.setAlignment(Qt.AlignCenter)

# 登录窗口
class LoginDialog(QDialog):
    login_successful = pyqtSignal()  # 定义登录成功信号

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("管理员登录")
        self.setFixedSize(300, 200)
        # 设置串口不可调整大小
        self.setFixedSize(self.size())

        main_layout = QVBoxLayout(self)

        # 创建分组：登录信息
        login_group = QGroupBox("登录信息")
        login_layout = QFormLayout()
        login_layout.setSpacing(10)

        self.password_label = QLabel("密码:")
        self.password_edit = QLineEdit()
        self.password_edit.setEchoMode(QLineEdit.Password)  # 设置密码显示为星号
        login_layout.addRow(self.password_label, self.password_edit)

        login_group.setLayout(login_layout)
        main_layout.addWidget(login_group)

        # 按钮布局
        button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        button_box.accepted.connect(self.check_password)
        button_box.rejected.connect(self.reject)
        main_layout.addWidget(button_box)

    def check_password(self):
        password = self.password_edit.text()
        if password == "123456":  # 假设正确密码为"correct_password"
            QMessageBox.information(self, "提示", "登录成功！")
            self.accept()
            self.login_successful.emit()  # 发出登录成功信号
        else:
            QMessageBox.warning(self, "警告", "密码错误，请重新输入！")

# 设备列表窗口
class DeviceListSerial(QDialog):
    def __init__(self, devices_list, parent=None):
        super().__init__(parent)
        self.setWindowTitle("OPGW设备配置")
        self.resize(800, 600)

        # 初始化设备列表数据
        self.devices_list = devices_list

        main_layout = QVBoxLayout(self)

        # 创建表单布局用于输入设备信息
        form_group = QGroupBox("设备信息输入")
        form_layout = QFormLayout()
        form_layout.setSpacing(10)  # 调整控件间距

        # 场景编号：使用 QSpinBox
        self.scene_number_spinbox = QSpinBox()
        self.scene_number_spinbox.setMinimum(0)  # 最小值为0
        self.scene_number_spinbox.setMaximum(999999)  # 设置一个较大的最大值
        self.scene_number_spinbox.setValue(0)  # 默认值为0
        form_layout.addRow("场景编号:", self.scene_number_spinbox)

        # 设备地址：使用 QSpinBox
        self.device_address_spinbox = QSpinBox()
        self.device_address_spinbox.setMinimum(1)  # 最小值为0
        self.device_address_spinbox.setMaximum(254)  # 设置一个较大的最大值
        self.device_address_spinbox.setValue(1)  # 默认值为0
        form_layout.addRow("设备地址:", self.device_address_spinbox)

        # 设备名称：保留 QLineEdit
        self.device_name_edit = QLineEdit()
        form_layout.addRow("设备名称:", self.device_name_edit)

        add_button = QPushButton("添加")
        add_button.clicked.connect(self.add_device)
        form_layout.addWidget(add_button)

        form_group.setLayout(form_layout)
        main_layout.addWidget(form_group)

        # 创建表格控件用于显示设备列表
        table_group = QGroupBox("设备列表")
        table_layout = QVBoxLayout()

        self.device_table = QTableWidget(0, 4)
        self.device_table.setHorizontalHeaderLabels(["序号", "场景编号", "设备地址", "设备名称"])
        self.device_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        table_layout.addWidget(self.device_table)

        button_layout = QHBoxLayout()  # 添加按钮布局
        delete_button = QPushButton("删除")
        delete_button.clicked.connect(self.delete_device)
        button_layout.addWidget(delete_button)

        save_button = QPushButton("保存")  # 新增保存按钮
        save_button.clicked.connect(self.accept)  # 关闭窗口并返回数据
        button_layout.addWidget(save_button)

        table_layout.addLayout(button_layout)  # 将按钮布局添加到表格布局中
        table_group.setLayout(table_layout)
        main_layout.addWidget(table_group)

        self.setLayout(main_layout)

        # 加载已有设备列表数据
        self.load_devices_list()

    def load_devices_list(self):
        """加载设备列表数据到表格"""
        self.device_table.setRowCount(0)  # 清空表格
        for idx, device in enumerate(self.devices_list):
            row_count = self.device_table.rowCount()
            self.device_table.insertRow(row_count)
            self.device_table.setItem(row_count, 0, QTableWidgetItem(str(idx + 1)))
            self.device_table.setItem(row_count, 1, QTableWidgetItem(device["场景编号"]))
            self.device_table.setItem(row_count, 2, QTableWidgetItem(device["设备地址"]))
            self.device_table.setItem(row_count, 3, QTableWidgetItem(device["设备名称"]))

    def add_device(self):
        """添加设备到表格"""
        scene_number = str(self.scene_number_spinbox.value())  # 获取场景编号
        device_address = str(self.device_address_spinbox.value())  # 获取设备地址
        device_name = self.device_name_edit.text()

        if not device_name:
            QMessageBox.warning(self, "警告", "请输入设备名称！")
            return

        # 添加到设备列表数据
        self.devices_list.append({
            "场景编号": scene_number,
            "设备地址": device_address,
            "设备名称": device_name
        })

        # 更新表格
        row_count = self.device_table.rowCount()
        self.device_table.insertRow(row_count)
        self.device_table.setItem(row_count, 0, QTableWidgetItem(str(row_count + 1)))
        self.device_table.setItem(row_count, 1, QTableWidgetItem(scene_number))
        self.device_table.setItem(row_count, 2, QTableWidgetItem(device_address))
        self.device_table.setItem(row_count, 3, QTableWidgetItem(device_name))

        # 清空输入框
        self.scene_number_spinbox.setValue(0)  # 重置场景编号
        self.device_address_spinbox.setValue(0)  # 重置设备地址
        self.device_name_edit.clear()

    def delete_device(self):
        """删除选中的设备"""
        selected_items = self.device_table.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, "警告", "请选择要删除的设备！")
            return

        row = selected_items[0].row()
        self.device_table.removeRow(row)

        # 从设备列表数据中删除
        del self.devices_list[row]

        # 更新序号
        for idx in range(self.device_table.rowCount()):
            self.device_table.item(idx, 0).setText(str(idx + 1))

    def get_devices_list(self):
        """返回当前设备列表数据"""
        return self.devices_list

# ICON 资源路径
def resource_path(relative_path):
    """获取资源的绝对路径"""
    if hasattr(sys, '_MEIPASS'):
        # PyInstaller 会将资源解压到 _MEIPASS 目录
        return os.path.join(sys._MEIPASS, relative_path)
    return os.path.join(os.path.abspath("."), relative_path)

# 主窗口
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("变电站OPGW终端监控系统V1.8")
        icon_path = resource_path("logo.ico")
        self.setWindowIcon(QIcon(icon_path))
        # 设置窗口大小
        self.resize(1600, 1000)
        # 设置打开就为全屏窗口
        self.setWindowState(Qt.WindowMaximized)

        # 创建菜单栏
        menu_bar = QMenuBar(self)
        self.setMenuBar(menu_bar)
        # 登录
        login_menu = menu_bar.addMenu("登录")
        # 采集控制
        collect_menu = menu_bar.addMenu("采集控制")
        # 系统设置
        system_menu = menu_bar.addMenu("系统设置")
        # 消息和日志
        log_menu = menu_bar.addMenu("消息和日志")

        # 将日志控件改为实例变量
        self.log_widget = QTextEdit()


        # 添加两个定时器
        self.device_timer = QTimer()
        self.resistance_timer = QTimer()

        # 初始化时间间隔变量和串口参数
        self.port = ""  # 默认串口
        self.baudrate = 115200 # 默认波特率
        self.device_interval = 5000  # 默认值5000毫秒
        self.resistance_interval = 10000  # 默认值10秒 (10000毫秒)

        # 连接定时器信号到槽函数
        self.device_timer.timeout.connect(self.update_device_sampling)
        self.resistance_timer.timeout.connect(self.update_resistance_sampling)

        # 添加设置选项
        # 设置参数
        settings_action = QAction("设置参数", self)
        settings_action.triggered.connect(self.show_settings_dialog)
        system_menu.addAction(settings_action)
        # 警告设置
        warning_action = QAction("警告设置", self)
        warning_action.triggered.connect(self.show_warning_settings_dialog)
        system_menu.addAction(warning_action)

        # 设备列表
        device_list_action = QAction("设备列表", self)
        device_list_action.triggered.connect(self.show_device_list)
        system_menu.addAction(device_list_action)

        # 采集控制
         # 开始采集
        start_collect_action = QAction("开始采集", self)
        start_collect_action.triggered.connect(self.start_collecting)  # 新增
        collect_menu.addAction(start_collect_action)
        
        # 停止采集
        stop_collect_action = QAction("停止采集", self)
        stop_collect_action.triggered.connect(self.stop_collecting)  # 新增
        collect_menu.addAction(stop_collect_action)

        # 登录
        # 管理员登录
        admin_login_action = QAction("管理员登录", self)
        admin_login_action.triggered.connect(self.show_login_dialog)
        login_menu.addAction(admin_login_action)
        # 退出登录
        logout_action = QAction("退出登录", self)
        logout_action.triggered.connect(self.logout)
        login_menu.addAction(logout_action)

        # 消息和日志
        # 清空日志
        clear_log_action = QAction("清空日志", self)
        clear_log_action.triggered.connect(self.log_widget.clear)
        log_menu.addAction(clear_log_action)

        # 创建主窗口部件
        main_widget = QWidget(self)
        self.setCentralWidget(main_widget)
        layout = QVBoxLayout(main_widget)

        # 设备数据存储结构（修改单位名称和设备名称）
        self.devices = {}  # 使用字典存储设备信息，设备地址作为键

        self.current_device_index = 0  # 当前设备索引

        # 创建表格控件
        self.table_widget = QTableWidget(0, 11)
        self.table_widget.setHorizontalHeaderLabels([
            "序号", 
            "单位名称", 
            "采集时间", 
            "设备地址", 
            "设备名称", 
            "电流方向", 
            "过压次数", 
            "雷击次数", 
            "电阻(Ω)", 
            "电流(A)", 
            "告警信息"
        ])
        self.table_widget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)

        # 设置表格为只读模式
        self.table_widget.setEditTriggers(QTableWidget.NoEditTriggers)

        layout.addWidget(self.table_widget)

        # 创建底部日志显示区域
        self.log_widget.setReadOnly(True)
        layout.addWidget(self.log_widget)

        #  添加日志文件路径变量
        date_str = datetime.now().strftime("%Y-%m-%d")
        self.log_file_path = os.path.join(os.path.abspath("."), f"{date_str}_opgw_logs.txt")

        # 创建状态栏
        self.status_bar = QStatusBar(self)
        self.setStatusBar(self.status_bar)
        self.status_bar.showMessage("等待用户状态登录")

        # 在MainWindow.__init__()中添加日志样式优化
        self.log_widget.setStyleSheet("""
            QTextEdit {
                font-family: Consolas;
                font-size: 30px;
                background-color: #1E1E1E;
                color: #D4D4D4;
            }
        """)

        self.write_log("【OPGW】: 服务状态等待启动...", Qt.white)
        self.is_logged_in = False  

        # 添加串口对象
        self.serial_port = None
        # 绑定串口数据接收信号
        if self.serial_port:
            self.serial_port.readyRead.connect(self.read_serial_data)

        # 添加设备列表数据存储变量
        self.devices_list = []  # 用于存储设备列表数据

        # 新增配置文件路径与告警设置默认值
        self.config_file_path = os.path.join(os.path.abspath("."), "config.json")
        self.warning_settings = {
            "resistance": "15",
            "induction_current": "60",
            "system_overvoltage": "30",
            "lightning_overvoltage": "50"
        }

        # 加载配置
        self.load_config()

        # 加载串口和采样时间设置
        if "serial_settings" in self.config:
            serial_cfg = self.config["serial_settings"]
            self.port = serial_cfg.get("port", self.port)
            self.baudrate = serial_cfg.get("baudrate", self.baudrate)
            self.device_interval = serial_cfg.get("device_interval", self.device_interval)
            self.resistance_interval = serial_cfg.get("resistance_interval", self.resistance_interval)

        self.resistance_no_response_count = 0  # 新增：记录连续未收到接地电阻响应的次数

    def load_config(self):
        """从本地文件读取配置"""
        if os.path.exists(self.config_file_path):
            try: # 添加try-except块以处理可能的JSON解码错误
                with open(self.config_file_path, "r", encoding="utf-8") as f:
                    self.config = json.load(f)
            except json.JSONDecodeError:
                self.write_log("【配置】: 配置文件格式错误，将使用默认配置。", Qt.red)
                self.config = {} # 如果文件格式错误，则创建一个空的配置字典
        else:
            self.config = {} # 如果文件不存在，则创建一个空的配置字典

    def save_config(self):
        """将当前设备列表、告警配置和串口设置写入本地文件"""
        self.config["devices_list"] = self.devices_list
        self.config["warning_settings"] = self.warning_settings
        # 保存串口和采样时间设置
        self.config["serial_settings"] = {
            "port": self.port,
            "baudrate": self.baudrate,
            "device_interval": self.device_interval,
            "resistance_interval": self.resistance_interval
        }
        with open(self.config_file_path, "w", encoding="utf-8") as f:
            json.dump(self.config, f, ensure_ascii=False, indent=4)

    def show_settings_dialog(self):
        # 传递当前设置给对话框
        current_settings = {
            "port": self.port,
            "baudrate": self.baudrate,
            "device_interval": self.device_interval,
            "resistance_interval": self.resistance_interval
        }
        dialog = SettingsDialog(current_settings, self)
        if dialog.exec_() == QDialog.Accepted:
            # 获取保存的设置参数
            settings = dialog.get_saved_settings()
            self.port = settings["port"]
            self.baudrate = int(settings["baudrate"])
            self.device_interval = int(settings["device_interval"])  # 设备采样时间间隔
            self.resistance_interval = int(settings["resistance_interval"]) * 1000  # 电阻采样时间间隔（转换为毫秒）
            print(f"保存设置: 通信端口={self.port}, 波特率={self.baudrate}, "
                  f"每设备采样时间间隔={self.device_interval}ms, 电阻采样时间间隔={self.resistance_interval}ms")
            # 保存配置到文件
            self.save_config()
            self.write_log("【系统】: 参数设置已保存。", Qt.white)

    def show_warning_settings_dialog(self):
        dialog = WarningSettingsDialog(self)
        # 将告警配置中的参数赋给对话框
        dialog.resistance_edit.setText(self.warning_settings["resistance"])
        dialog.induction_current_edit.setText(self.warning_settings["induction_current"])
        dialog.system_overvoltage_edit.setText(self.warning_settings["system_overvoltage"])
        dialog.lightning_overvoltage_edit.setText(self.warning_settings["lightning_overvoltage"])

        if dialog.exec_() == QDialog.Accepted:
            # 保存更新后的数据
            self.warning_settings["resistance"] = dialog.resistance_edit.text()
            self.warning_settings["induction_current"] = dialog.induction_current_edit.text()
            self.warning_settings["system_overvoltage"] = dialog.system_overvoltage_edit.text()
            self.warning_settings["lightning_overvoltage"] = dialog.lightning_overvoltage_edit.text()
            self.save_config()
    def show_login_dialog(self):
        dialog = LoginDialog(self)
        dialog.login_successful.connect(self.update_status_bar)  # 连接信号到槽
        if dialog.exec_() == QDialog.Accepted:
            print("登录成功，继续其他操作...")
    def update_status_bar(self):
        self.status_bar.showMessage("管理员已登录")
        self.is_logged_in = True  # 设置登录状态为True
    def show_device_list(self):
        """显示设备列表窗口，并传递设备列表数据"""
        dialog = DeviceListSerial(self.devices_list, parent=self)
        if dialog.exec_() == QDialog.Accepted:
            # 更新设备列表数据
            self.devices_list = dialog.get_devices_list()
            # 将设备列表数据同步到主界面表格
            self.update_main_table_from_devices_list()
            # 设备列表更新后保存配置
            self.save_config()
    def update_main_table_from_devices_list(self):
        """将设备列表数据同步到主界面表格，并更新 self.devices"""
        # 清空表格内容
        self.table_widget.setRowCount(0)
        # 清空 self.devices
        self.devices.clear()

        # 遍历设备列表，逐行添加数据
        for idx, device in enumerate(self.devices_list):
            row_count = self.table_widget.rowCount()
            self.table_widget.insertRow(row_count)

            # 填充表格数据，并设置居中对齐
            items = [
                QTableWidgetItem(str(idx + 1)),  # 序号
                QTableWidgetItem("国家电网"),  # 单位名称
                QTableWidgetItem(""),  # 采集时间（留空）
                QTableWidgetItem(device["设备地址"]),  # 设备地址
                QTableWidgetItem(device["设备名称"]),  # 设备名称
                QTableWidgetItem(""),  # 电流方向（留空）
                QTableWidgetItem("0"),  # 过压次数
                QTableWidgetItem("0"),  # 雷击次数
                QTableWidgetItem("0"),  # 电阻
                QTableWidgetItem("0"),  # 电流
                QTableWidgetItem("")  # 告警信息（留空）
            ]

            for col, item in enumerate(items):
                item.setTextAlignment(Qt.AlignCenter)  # 设置居中对齐
                self.table_widget.setItem(row_count, col, item)

            # 将设备对象添加到 self.devices 中
            device_id = int(device["设备地址"])  # 设备地址作为键
            self.devices[device_id] = {
                "单位名称": "电力公司",
                "设备名称": device["设备名称"],
                "采集时间": "",  # 初始为空
                "最新电阻": "0Ω",
                "最新电流": "0A",
                "过压次数": 0,
                "雷击次数": 0,
                "告警信息": "正常"  # 默认告警状态
            }
    def logout(self):
        self.is_logged_in = False
        self.status_bar.showMessage("等待用户状态登录")
    def open_serial_port(self):
        """打开串口并返回是否成功"""
        try:
            # 如果串口已经打开，先关闭
            if self.serial_port and self.serial_port.isOpen():
                self.serial_port.close()

            # 创建新的串口对象
            self.serial_port = QSerialPort()
            self.serial_port.setPortName(self.port)  # 设置端口号
            self.serial_port.setBaudRate(int(self.baudrate))  # 设置波特率

            # 打开串口
            if self.serial_port.open(QSerialPort.ReadWrite):
                self.write_log(f"【串口】: 成功打开串口 {self.port}，波特率 {self.baudrate}", Qt.green)
                
                # 绑定串口数据接收信号
                self.serial_port.readyRead.connect(self.read_serial_data)
                return True
            else:
                self.write_log(f"【串口】: 打开串口失败", Qt.red)
                return False
        except Exception as e:
            self.write_log(f"【串口】: 打开串口失败，错误信息：{str(e)}", Qt.red)
            return False
    def read_serial_data(self):
        """读取串口数据并解析"""
        if not self.serial_port or not self.serial_port.isOpen():
            self.write_log(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 【串口】: 串口未打开，无法接收数据", Qt.red)
            return

        try:
            # 读取串口缓冲区中的所有数据
            raw_data = self.serial_port.readAll().data()
            
            # 格式化原始数据为十六进制字符串
            formatted_raw_data = ' '.join(f"0x{byte:02X}" for byte in raw_data)

            # 打印接收到的原始数据到日志
            self.write_log(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 【OPGW-RX-RAW】: {formatted_raw_data}", Qt.cyan)

            # 确保数据长度为21字节
            # if len(raw_data) != 21:
            #     self.write_log(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 【OPGW-RX】: 数据长度错误，丢弃数据", Qt.red)
            #     return

            # 提取有效数据部分和CRC校验值
            data_part = raw_data[:-2]  # 前19字节为有效数据
            received_crc = int.from_bytes(raw_data[-2:], byteorder='little')  # 最后2字节为CRC校验值

            # 计算接收到的有效数据部分的 CRC 校验值
            calculated_crc = self.crc16(data_part)

            # 比较 CRC 校验值
            if received_crc != calculated_crc:
                self.write_log(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 【OPGW-RX-CRC-ERROR】: CRC校验失败", Qt.red)
                return

            # CRC 校验通过，解析数据
            self.parse_received_data(data_part)

        except Exception as e:
            self.write_log(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 【OPGW-RX】: 接收数据失败，错误信息：{str(e)}", Qt.red)
    def parse_received_data(self, data):
        """解析接收到的数据并更新表格"""
        try:
            # 第一个字节为设备地址
            device_address = data[0]

            # 检查设备地址是否已经存在
            if device_address not in self.devices:
                # 动态添加新设备到 self.devices 字典
                self.devices[device_address] = {
                    "单位名称": "国家电网",
                    "设备名称": "综合数据采集模块",  # 默认设备名称
                    "采集时间": "",  # 初始为空
                    "最新电阻": "0Ω",
                    "最新电流": "0A",
                    "过压次数": 0,
                    "雷击次数": 0,
                    "告警信息": "正常"  # 默认告警状态
                }
                # 记录日志
                self.write_log(f"[{datetime.now().strftime('%H:%M:%S')}] 【OPGW-RX】: 新增设备地址 0x{device_address:02X}", Qt.yellow)

            if device_address == 0x43:
                # 接地电阻单独解析
                # 第四、第五个字节组成电阻值，先转换成十进制然后除以100
                resistance = int.from_bytes(data[3:5], byteorder='big') / 100
                # 其他数据全为0
                current = 0
                direction = 0
                overvoltage_count = 0
                lightning_count = 0
                self.resistance_no_response_count = 0  # 如果正确接收到接地电阻设备ID(0x43)的回复，则重置计数
            else:
                # 第四、第五个字节组成方向值
                direction = int.from_bytes(data[3:5], byteorder='big')

                # 第八、第九个字节组成过压次数
                overvoltage_count = int.from_bytes(data[7:9], byteorder='big')

                # 第十、第十一个字节组成雷击次数
                lightning_count = int.from_bytes(data[9:11], byteorder='big')

                # 第十二到第十五个字节组成电阻值（IEEE754格式）
                resistance_bytes = data[13:15] + data[11:13] 
                resistance = struct.unpack('>f', resistance_bytes)[0]

                # 第十六到第十九个字节组成电流值（IEEE754格式）
                current_bytes = data[17:19] + data[15:17]
                current = struct.unpack('>f', current_bytes)[0]

            # 更新设备数据
            self.update_device_data(device_address, direction, overvoltage_count, lightning_count, resistance, current)

            # 记录日志
            self.write_log(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 【OPGW-RX】: 数据解析成功", Qt.green)

        except Exception as e:
            self.write_log(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 【OPGW-RX】: 数据解析失败，错误信息：{str(e)}", Qt.red)
    def update_device_data(self, device_address, direction, overvoltage_count, lightning_count, resistance, current):
        """更新设备数据到表格"""
        # 查找设备对应的行号
        row = -1
        for i in range(self.table_widget.rowCount()):
            table_address = int(self.table_widget.item(i, 3).text())  # 转换为整数
            if table_address == device_address:
                row = i
                break

        # 如果不存在则创建新行
        if row == -1:
            row = self.table_widget.rowCount()
            self.table_widget.insertRow(row)

            # 初始化固定列，并设置居中对齐
            items = [
                QTableWidgetItem(str(row + 1)),  # 序号
                QTableWidgetItem("国家电网"),  # 单位名称
                QTableWidgetItem(""),  # 采集时间（留空）
                QTableWidgetItem(str(device_address)),  # 设备地址
                QTableWidgetItem("综合数据采集模块"),  # 设备名称
                QTableWidgetItem(""),  # 电流方向（留空）
                QTableWidgetItem("0"),  # 过压次数
                QTableWidgetItem("0"),  # 雷击次数
                QTableWidgetItem("0"),  # 电阻
                QTableWidgetItem("0"),  # 电流
                QTableWidgetItem("")  # 告警信息（留空）
            ]

            if device_address == 0x43:
                items[4].setText("总线接地电阻")

            for col, item in enumerate(items):
                item.setTextAlignment(Qt.AlignCenter)  # 设置居中对齐
                self.table_widget.setItem(row, col, item)

        # 更新动态数据，并设置居中对齐
        items = [
            QTableWidgetItem(str(direction)),  # 电流方向
            QTableWidgetItem(str(overvoltage_count)),  # 过压次数
            QTableWidgetItem(str(lightning_count)),  # 雷击次数
            QTableWidgetItem(f"{resistance:.1f}Ω"),  # 电阻
            QTableWidgetItem(f"{current:.1f}A")  # 电流
        ]

        # 更新采集时间（单独更新）
        timestamp_item = QTableWidgetItem(datetime.now().strftime("%H:%M:%S"))
        timestamp_item.setTextAlignment(Qt.AlignCenter)
        self.table_widget.setItem(row, 2, timestamp_item)  # 第3列为采集时间

        # 从第4列开始更新（电流方向）
        for col_offset, item in enumerate(items, start=5):  # 从第4列开始
            item.setTextAlignment(Qt.AlignCenter)  # 设置居中对齐
            self.table_widget.setItem(row, col_offset, item)

        # 添加告警信息判断
        alarm_info = "正常"
        alarm_color = Qt.black  # 默认字体颜色
        if resistance > float(self.warning_settings["resistance"]):
            alarm_info = "电阻值异常"
            alarm_color = Qt.red
        elif current > float(self.warning_settings["induction_current"]):
            alarm_info = "感应电流值异常"
            alarm_color = Qt.red

        self.devices[device_address].update({
            "采集时间": datetime.now().strftime("%H:%M:%S"),
            "最新电阻": f"{resistance:.1f}Ω",
            "最新电流": f"{current:.1f}A",
            "过压次数": overvoltage_count,
            "雷击次数": lightning_count,
            "告警信息": alarm_info
        })

        # 更新表格中的告警信息列
        alarm_item = QTableWidgetItem(alarm_info)
        alarm_item.setTextAlignment(Qt.AlignCenter)
        if alarm_info != "正常":
            alarm_item.setForeground(alarm_color)
        self.table_widget.setItem(row, 10, alarm_item)

        # 将解析到的数据保存到日志文件中
        with open(self.log_file_path, "a", encoding="utf-8") as log_file:
            log_file.write(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] "
                           f"设备地址: {device_address}, 电流方向: {direction}, "
                           f"过压次数: {overvoltage_count}, 雷击次数: {lightning_count}, "
                           f"电阻: {resistance:.1f}Ω, 电流: {current:.1f}A, 告警信息: {alarm_info}\n")
            # 新增：如果有告警，单独写入一条警告日志
            if alarm_info != "正常":
                log_file.write(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] [警告] 设备地址: {device_address} - {alarm_info}\n")

    def start_collecting(self):
        if not self.is_logged_in:
            QMessageBox.warning(
                self, 
                "权限不足",
                "请先登录管理员账号！",
                QMessageBox.Ok
            )
            return  # 中断操作
        
        # 检查设备列表是否为空
        if not self.devices_list:
            QMessageBox.warning(
                self,
                "设备列表为空",
                "请先添加设备到设备列表！",
                QMessageBox.Ok
            )
            return  # 中断操作

        # 打开串口
        if not self.open_serial_port():
            QMessageBox.critical(
                self, 
                "串口错误",
                "无法打开串口，请检查设置！",
                QMessageBox.Ok
            )
            return
        
        # 初始化设备索引
        self.current_device_index = 0

        # 启动设备采样定时器
        self.device_timer.start(self.device_interval)
        self.write_log(f"【系统】: 设备采样已启动，时间间隔={self.device_interval}ms...", Qt.white)

        # 延迟启动电阻采样定时器，避免同时发送
        QTimer.singleShot(203, lambda: self.resistance_timer.start(self.resistance_interval))
        self.write_log(f"【系统】: 电阻采样将于200ms后启动，时间间隔={self.resistance_interval}ms...", Qt.white)
    def stop_collecting(self):
        """停止采集（添加登录验证）"""
        if not self.is_logged_in:
            QMessageBox.warning(
                self, 
                "权限不足",
                "请先登录管理员账号！",
                QMessageBox.Ok
            )
            return  # 中断操作
        
        # 停止设备采样定时器
        self.device_timer.stop()
        self.write_log("【系统】: 设备采样已停止...", Qt.white)

        # 停止电阻采样定时器
        self.resistance_timer.stop()
        self.write_log("【系统】: 电阻采样已停止...", Qt.white)

        # 关闭串口
        if self.serial_port and self.serial_port.isOpen():
            self.serial_port.close()
            self.write_log(f"【串口】: 串口 {self.port} 已关闭", Qt.green)
        
        # 断开串口数据接收信号连接
        if self.serial_port:
            try:
                self.serial_port.readyRead.disconnect(self.read_serial_data)
            except TypeError:
                pass  # 如果信号未连接，忽略异常
    def update_resistance_sampling(self):
        # 如果已经连续10次未收到接地电阻设备应答，则打印警告信息
        if self.resistance_no_response_count >= 10:
            self.write_log(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 【OPGW-WARNNING】: 接地电阻设备离线, 请检查设备连接!", Qt.red)

        # 检查串口是否打开
        if not self.serial_port or not self.serial_port.isOpen():
            self.write_log("【OPGW-TX】: 串口未打开，无法发送电阻采样指令", Qt.red)
            return
        # 发送 0x43 0x03 0x00 0x00 0x00 0x02 0xCB 0x29
        data_to_send = bytes([0x43, 0x03, 0x00, 0x00, 0x00, 0x02, 0xCB, 0x29])
        self.serial_port.write(data_to_send)
        formatted_data = " ".join([f"0x{b:02X}" for b in data_to_send])
        self.write_log(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 【OPGW-TX-RESISTANCE】: {formatted_data}", Qt.green)

        # 每次发送获取接地电阻指令后，计数器加1
        self.resistance_no_response_count += 1
    def update_device_sampling(self):
        """更新设备采样数据"""
        # 检查设备列表是否为空
        if not self.devices_list:
            self.write_log("【OPGW-TX】: 设备列表为空，无法发送数据", Qt.red)
            return

        # 检查串口是否打开
        if not self.serial_port or not self.serial_port.isOpen():
            self.write_log(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 【OPGW-TX】: 串口未打开，无法发送数据", Qt.red)
            return

        try:
            # 获取当前设备地址
            current_device = self.devices_list[self.current_device_index]
            device_address = int(current_device["设备地址"])

            # 构造数据帧
            raw_data = bytes([
                device_address,  # 第一个字节：设备地址
                0x03,           # 功能码
                0x00, 0x00,     # 寄存器起始地址
                0x00, 0x08       # 寄存器数量
            ])

            # 计算CRC16校验值
            crc_value = self.crc16(raw_data)
            crc_bytes = crc_value.to_bytes(2, byteorder='little')  # 转换为小端字节序

            # 构造完整的数据帧
            data_to_send = raw_data + crc_bytes

            # 发送数据
            self.serial_port.write(data_to_send)

            # 格式化输出
            formatted_data = ' '.join(f"0x{byte:02X}" for byte in data_to_send)

            # 记录日志
            self.write_log(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 【OPGW-TX】: {formatted_data}", Qt.green)

            # 更新设备索引
            self.current_device_index += 1
            if self.current_device_index >= len(self.devices_list):
                self.current_device_index = 0  # 循环回到第一个设备

        except Exception as e:
            self.write_log(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 【OPGW-TX】: 发送数据失败，错误信息：{str(e)}", Qt.red)
    def update_table(self, device_id):
        """更新指定设备的表格行"""
        # 查找设备对应的行号
        row = -1
        for i in range(self.table_widget.rowCount()):
            if self.table_widget.item(i, 3).text() == str(device_id):
                row = i
                break

        # 如果不存在则创建新行
        if row == -1:
            row = self.table_widget.rowCount()
            self.table_widget.insertRow(row)

            # 初始化固定列，并设置居中对齐
            items = [
                QTableWidgetItem(str(row + 1)),  # 序号
                QTableWidgetItem("电力公司"),  # 单位名称
                QTableWidgetItem(""),  # 采集时间（留空）
                QTableWidgetItem(str(device_id)),  # 设备地址
                QTableWidgetItem("综合数据采集模块"),  # 设备名称
                QTableWidgetItem(""),  # 电流方向（留空）
                QTableWidgetItem("0"),  # 过压次数
                QTableWidgetItem("0"),  # 雷击次数
                QTableWidgetItem("0"),  # 电阻
                QTableWidgetItem("0"),  # 电流
                QTableWidgetItem("")  # 告警信息（留空）
            ]

            for col, item in enumerate(items):
                item.setTextAlignment(Qt.AlignCenter)  # 设置居中对齐
                self.table_widget.setItem(row, col, item)

        # 更新动态数据，并设置居中对齐
        data = self.devices[device_id]
        alarm_info = data["告警信息"]  # 直接使用告警状态字段

        items = [
            QTableWidgetItem(data["采集时间"]),  # 采集时间
            QTableWidgetItem(random.choice(["正向", "反向"])),  # 电流方向
            QTableWidgetItem(str(data["过压次数"])),  # 过压次数
            QTableWidgetItem(str(data["雷击次数"])),  # 雷击次数
            QTableWidgetItem(data["最新电阻"]),  # 电阻
            QTableWidgetItem(data["最新电流"]),  # 电流
            QTableWidgetItem(alarm_info)  # 告警信息
        ]

        for col, item in enumerate(items, start=2):  # 从第3列开始更新
            item.setTextAlignment(Qt.AlignCenter)  # 设置居中对齐
            self.table_widget.setItem(row, col, item)
    def crc16(self, data):
        crc = 0xFFFF
        for byte in data:
            crc ^= byte
            for _ in range(8):
                if crc & 0x0001:
                    crc = (crc >> 1) ^ 0xA001
                else:
                    crc = crc >> 1
        return crc
    def write_log(self, message, color=Qt.white):
        """统一处理日志输出到界面与本地文件"""
        self.log_widget.setTextColor(color)
        self.log_widget.append(message)
        with open(self.log_file_path, "a", encoding="utf-8") as f:
            f.write(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {message}\n")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())